<!DOCTYPE html>
<html>

<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<style>
html {
    text-align: center;
}
body {
    margin-top: 20px;
}
#buttons {
    margin-top: 10px;
}
</style>
</head>

<body>
<canvas id="game" width="400" height="450"
style="border:1px solid #d3d3d3;">
Your browser does not support the canvas element.
</canvas>

<div id="buttons">
     <button type="button">简单</button> 
     <button type="button">困难</button> 
     <button type="button">悔棋</button> 
</div>

<script>
/* Global const */
const canvas = document.getElementById("game")
const ctx = canvas.getContext("2d")
const row = 10, col = 9
const cellSize = 50, padding = 40
const sharpLength = cellSize * 0.20
const sharpMargin = sharpLength * 0.3
const pieceOuterRadius = cellSize * 0.42
const pieceInnerRadius = pieceOuterRadius * 0.85
const pieceTextFont = "23px SimHei"
const width = (col - 1) * cellSize + 2 * padding
const height = (row - 1) * cellSize + 2 * padding
const names = [["R0", "H0", "E0", "A0", "G", "A1", "E1", "H1", "R1", "C0", "C1", "S0", "S1", "S2", "S3", "S4"],
               ["r0", "h0", "e0", "a0", "g", "a1", "e1", "h1", "r1", "c0", "c1", "s0", "s1", "s2", "s3", "s4"]]
const texts = {"R":"俥", "H":"傌", "E":"相", "A":"仕", "G":"帥", "C":"炮", "S":"兵",
               "r":"車", "h":"馬", "e":"象", "a":"士", "g":"將", "c":"砲", "s":"卒"}

canvas.width = width
canvas.height = height



/* Maintained variables */
var gamestate   // 0 for ready, 1 for my turn, 2 for opponent turn
var myColor     // 0 for red, 1 for black
var searchLv    // the max time for opponent to think, the larger the harder
var board       // what is the piece at given position (x, y)?
var piece       // what the position of a given piece?
var steps       // record each step in order to undo
var chosenCoor  // coordinate that has been chosen by player



/* Model */
function coor2scr(x, y) {
    return [padding + x * cellSize, padding + (row - 1 - y) * cellSize]
}

function scr2coor(sx, sy) {
    if (sx < padding - cellSize / 2 || sx > width - padding + cellSize / 2 ||
        sy < padding - cellSize / 2 || sy > height - padding + cellSize / 2) {
        return [-1, -1]
    }
    unit_sx = Math.round((sx - padding) / cellSize)
    unit_sy = Math.round((sy - padding) / cellSize)
    return [unit_sx, row - 1 - unit_sy]
}

function isMyPiece(pieceName) {
    return Boolean(+(pieceName.charCodeAt(0) <= 90) ^ myColor)
}

function init(myColor_, searchLv_) {
    gamestate = 1
    myColor = myColor_
    searchLv = searchLv_

    piece = {}
    for (let i = 0; i < 2; ++i) {
        const color = myColor ^ i
        let y = (i == 0 ? 0 : row - 1)
        for (let x = 0; x < col; ++x) {
            piece[names[color][x]] = [x, y]
        }
        y = (i == 0 ? 2 : row - 1 - 2)
        for (let x = 0; x < 2; ++x) {
            piece[names[color][x + 9]] = [1 + 6 * x, y]
        }
        y = (i == 0 ? 3 : row - 1 - 3)
        for (let x = 0; x < 5; ++x) {
            piece[names[color][x + 11]] = [x * 2, y]
        }
    }

    board = []
    for (let x = 0; x < col; ++x) {
        let tem = []
        for (let y = 0; y < row; ++y) {
            tem.push("")
        }
        board.push(tem)
    }
    for (let name in piece) {
        board[piece[name][0]][piece[name][1]] = name
    }

    steps = []
    chosenCoor = [-1, -1]
}

// return true if choose successfully and false elsewhere
function choose(x, y) {
    if (x == -1 || y == -1) {
        return false
    }
    if (board[x][y] === "") {
        return false
    }
    if (!isMyPiece(board[x][y])) {
        return false
    }

    chosenCoor = [x, y]
    return true
}

// return true if move successfully and false elsewhere
function move(x0, y0, x1, y1) {
    if (x0 == -1 || y0 == -1 || x1 == -1 || y1 == -1) {
        return false
    }
    if (x0 == x1 && y0 == y1) {
        return false
    }
    if (board[x0][y0] === "") {
        return false
    }
    var isMyTurn = isMyPiece(board[x0][y0])
    if (board[x1][y1] !== "") {
        if (+isMyTurn ^ +isMyPiece(board[x1][y1]) == 0) {
            return false
        }
    }
    var myPieceType = board[x0][y0].charAt(0).toUpperCase()
    switch (myPieceType) {
        case "R":
            if (x0 != x1 && y0 != y1) {
                return false
            }
            var numStep = Math.abs(x1 - x0) + Math.abs(y1 - y0)
            var dx = Math.trunc((x1 - x0) / numStep)
            var dy = Math.trunc((y1 - y0) / numStep)
            var cx = x0 + dx, cy = y0 + dy
            while (cx != x1 || cy != y1) {
                if (board[cx][cy] !== "") {
                    return false
                }
                cx += dx
                cy += dy
            }
            break;
        case "H":
            var dx = x1 - x0, dy = y1 - y0
            var adx = Math.abs(x1 - x0), ady = Math.abs(y1 - y0)
            if (adx + ady != 3 || Math.abs(adx - ady) != 1) {
                return false
            }
            // deal with BieMaTui
            dx = Math.trunc(dx / 2)
            dy = Math.trunc(dy / 2)
            if (board[x0 + dx][y0 + dy] !== "") {
                return false
            }
            break;
        case "E":
            if (!(isMyTurn && y1 < 5 || !isMyTurn && y1 >= 5)) {
                return false
            }
            var dx = x1 - x0, dy = y1 - y0
            if (Math.abs(dx) != 2 || Math.abs(dy) != 2) {
                return false
            }
            // deal with BieXiangTui
            dx = Math.trunc(dx / 2)
            dy = Math.trunc(dy / 2)
            if (board[x0 + dx][y0 + dy] !== "") {
                return false
            }
            break;
        case "A":
            var bx = 4, by = (isMyTurn ? 1 : 8);
            var ex = x1 - bx, ey = y1 - by
            if (Math.abs(ex) != Math.abs(ey) || Math.abs(ex) > 1) {
                return false
            }
            var dx = x1 - x0, dy = y1 - y0
            if (Math.abs(dx) != Math.abs(dy) || Math.abs(dx) != 1) {
                return false
            }
            break;
        case "G":
            // deal with FeiJiangJun
            if (board[x1][y1] !== "" && board[x1][y1].charAt(0).toUpperCase() === "G") {
                var numStep = Math.abs(x1 - x0) + Math.abs(y1 - y0)
                var dx = Math.trunc((x1 - x0) / numStep)
                var dy = Math.trunc((y1 - y0) / numStep)
                var cx = x0 + dx, cy = y0 + dy
                while (cx != x1 || cy != y1) {
                    if (board[cx][cy] !== "") {
                        return false
                    }
                    cx += dx
                    cy += dy
                }
                break;
            }
            var bx = 4, by = (isMyTurn ? 1 : 8);
            var ex = x1 - bx, ey = y1 - by
            if (Math.max(Math.abs(ex), Math.abs(ey)) > 1) {
                return false
            }
            var dx = x1 - x0, dy = y1 - y0
            if (Math.abs(dx) + Math.abs(dy) > 1) {
                return false
            }
            break;
        case "C":
            if (x0 != x1 && y0 != y1) {
                return false
            }
            var numStep = Math.abs(x1 - x0) + Math.abs(y1 - y0)
            var dx = Math.trunc((x1 - x0) / numStep)
            var dy = Math.trunc((y1 - y0) / numStep)
            var cx = x0 + dx, cy = y0 + dy
            var lmt = (board[x1][y1] === "" ? 0 : 1)
            var cnt = 0
            while (cx != x1 || cy != y1) {
                if (board[cx][cy] !== "") {
                    if (++cnt > lmt) {
                        return false
                    }
                }
                cx += dx
                cy += dy
            }
            if (cnt != lmt) {
                return false
            }
            break;
        case "S":
            var validDy = (isMyTurn ? 1 : -1)
            var dx = x1 - x0, dy = y1 - y0
            if (isMyTurn && y0 < 5 || !isMyTurn && y1 >= 5) {
                if (!(dx == 0 && dy == validDy)) {
                    return false
                }
            }
            else {
                if (dx == 0) {
                    if (dy != validDy) {
                        return false
                    }
                }
                else {
                    if (dy != 0 || Math.abs(dx) != 1) {
                        return false
                    }
                }
            }
            break;
        default:
            console.error("unknown piece: " + board[x0][y0])
    }

    var killer = board[x0][y0]
    var victim = board[x1][y1]

    steps.push([x0, y0, x1, y1, victim])

    if (!(victim === "")) {
        piece[victim] = [-1, -1]
    }
    piece[killer] = [x1, y1]
    
    board[x1][y1] = killer
    board[x0][y0] = ""
    
    chosenCoor = [-1, -1]

    gamestate = 3 - gamestate

    return true
}

function opponentTurn(callback) {
    var xhr = new XMLHttpRequest()
    xhr.onreadystatechange = function() {
        if (this.readyState == 4 && this.status == 200) {
            var result = JSON.parse(this.responseText)
            callback(move(result.x0, result.y0, result.x1, result.y1))
        }
    }
    xhr.open("POST", "nextmove", true)
    var r = steps.length + "," + Math.max(0, searchLv)
    for (var pieceName in piece) {
        if (piece[pieceName][0] != -1 && piece[pieceName][1] != -1) {
            r = r + pieceName.charAt(0) + piece[pieceName][0] + piece[pieceName][1]
        }
    }
    r = r + "."
    console.log(r)
    xhr.send(r)
}



/* Controller */
canvas.addEventListener("click", function(e) {
    if (gamestate != 1) {
        return
    }
    const rect = canvas.getBoundingClientRect()
    const x = e.clientX - rect.left
    const y = e.clientY - rect.top
    c = scr2coor(x, y)
    if (choose(c[0], c[1])) {
        update()
    }
    else if (move(chosenCoor[0], chosenCoor[1], c[0], c[1])) {
        update()
        opponentTurn(function(ok) {
            if (ok) {
                update()
            }
            else {
                console.error("Something went wrong with backend")
            }
        })
    }
}, false)




/* View */
function drawSharp(r, c) {
    sx = padding + c * cellSize
    sy = padding + r * cellSize
    if (c < col - 1) {
        ctx.moveTo(sx + sharpMargin, sy - sharpLength)
        ctx.lineTo(sx + sharpMargin, sy - sharpMargin)
        ctx.lineTo(sx + sharpLength, sy - sharpMargin)
        ctx.stroke()
        ctx.moveTo(sx + sharpMargin, sy + sharpLength)
        ctx.lineTo(sx + sharpMargin, sy + sharpMargin)
        ctx.lineTo(sx + sharpLength, sy + sharpMargin)
        ctx.stroke()
    }
    if (c > 0) {
        ctx.moveTo(sx - sharpMargin, sy - sharpLength)
        ctx.lineTo(sx - sharpMargin, sy - sharpMargin)
        ctx.lineTo(sx - sharpLength, sy - sharpMargin)
        ctx.stroke()
        ctx.moveTo(sx - sharpMargin, sy + sharpLength)
        ctx.lineTo(sx - sharpMargin, sy + sharpMargin)
        ctx.lineTo(sx - sharpLength, sy + sharpMargin)
        ctx.stroke()
    }
}

function convertRow(r, party) {
    return party == 0 ? r : row - 1 - r
}

function drawPiece(x, y, pieceName) {
    if (x < 0 || x >= col || y < 0 || y >= row) {
        return
    }
    sc = coor2scr(x, y)
    ctx.save()
    if (x == chosenCoor[0] && y == chosenCoor[1]) {
        ctx.globalAlpha = 0.7
    }
    ctx.fillStyle = "rgb(254, 218, 164)"
    ctx.beginPath()
    ctx.arc(sc[0], sc[1], pieceOuterRadius, 0, 2 * Math.PI)
    ctx.fill()

    ctx.strokeStyle = "black"
    ctx.lineWidth = 1.5
    ctx.beginPath()
    ctx.arc(sc[0], sc[1], pieceOuterRadius, 0, 2 * Math.PI)
    ctx.stroke()

    if (pieceName.charCodeAt(0) <= 90) {
        ctx.strokeStyle = "red"
        ctx.fillStyle = "red"
    }
    else {
        ctx.strokeStyle = "black"
        ctx.fillStyle = "black"
    }
    ctx.beginPath()
    ctx.arc(sc[0], sc[1], pieceInnerRadius, 0, 2 * Math.PI)
    ctx.stroke()

    ctx.textBaseline = "middle"
    ctx.textAlign = "center"
    ctx.font = pieceTextFont;
    ctx.fillText(texts[pieceName.charAt(0)], sc[0], sc[1])

    ctx.restore()
}

function update() {
    ctx.clearRect(0, 0, canvas.width, canvas.height)

    /* background */
    ctx.fillStyle = "rgb(206, 92, 0)"
    ctx.fillRect(0, 0, width, height)
    ctx.lineWidth = 5
    ctx.strokeStyle = "black"
    ctx.strokeRect(0, 0, width, height)

    /* chessboard */
    ctx.fillStyle = "rgb(252, 175, 62)"
    ctx.fillRect(padding, padding, (col - 1) * cellSize, (row - 1) * cellSize)
    ctx.lineWidth = 1.5
    ctx.strokeRect(padding, padding, (col - 1) * cellSize, (row - 1) * cellSize)
    for (var r = 1; r < row - 1; ++r) {
        ctx.moveTo(padding, padding + r * cellSize)
        ctx.lineTo(width - padding, padding + r * cellSize)
        ctx.stroke()
    }
    for (var c = 1; c < col - 1; ++c) {
        const tem = padding + c * cellSize
        ctx.moveTo(tem, padding)
        ctx.lineTo(tem, padding + 4 * cellSize) 
        ctx.stroke()
        ctx.moveTo(tem, height - padding)
        ctx.lineTo(tem, height - padding - 4 * cellSize)
        ctx.stroke()
    }
    for (var i = 0; i < 2; ++i) {
        ctx.moveTo(padding + 3 * cellSize, padding + convertRow(0, i) * cellSize)
        ctx.lineTo(padding + 5 * cellSize, padding + convertRow(2, i) * cellSize)
        ctx.stroke()
        ctx.moveTo(padding + 5 * cellSize, padding + convertRow(0, i) * cellSize)
        ctx.lineTo(padding + 3 * cellSize, padding + convertRow(2, i) * cellSize)
        ctx.stroke()
        drawSharp(convertRow(2, i), 1)
        drawSharp(convertRow(2, i), 7)
        for (var c = 0; c < col; c += 2) {
            drawSharp(convertRow(3, i), c)
        }
    }

    /* pieces */
    if (piece != null) {
        for (let name in piece) {
            drawPiece(piece[name][0], piece[name][1], name)
        }
    }
}

init(0, 2)
update()

</script>

</body>
</html>
